package org.travelfusion.xmlclient.ri.handler.misc;

import java.lang.reflect.Method;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;

import javax.xml.stream.XMLStreamConstants;

import org.travelfusion.xmlclient.handler.HandlesRequestsFor;
import org.travelfusion.xmlclient.impl.handler.AbstractXmlToolRequestStAXResponseHandler;
import org.travelfusion.xmlclient.ri.xobject.misc.XFindAlternativeFlightsConstants;
import org.travelfusion.xmlclient.ri.xobject.misc.XFindAlternativeFlightsItem;
import org.travelfusion.xmlclient.ri.xobject.misc.XFindAlternativeFlightsItem.CodeType;
import org.travelfusion.xmlclient.ri.xobject.misc.XFindAlternativeFlightsLegDetails;
import org.travelfusion.xmlclient.ri.xobject.misc.XFindAlternativeFlightsRequest;
import org.travelfusion.xmlclient.ri.xobject.misc.XFindAlternativeFlightsResponse;
import org.travelfusion.xmlclient.util.TfXAPIUtil;

import com.mycila.xmltool.XMLTag;

@HandlesRequestsFor(XFindAlternativeFlightsRequest.class)
public class FindAlternativeFlightsHandler extends
    AbstractXmlToolRequestStAXResponseHandler<XFindAlternativeFlightsRequest, XFindAlternativeFlightsResponse> {
  private static enum CurrentLeg {
    OUTWARD, RETURN
  };

  private final DateFormat DFT = new SimpleDateFormat(TfXAPIUtil.TRAVELFUSION_TRIPPLANNER_XML_DATE_FORMAT);
  {
    DFT.setTimeZone(TimeZone.getTimeZone("GMT"));
  }

  @Override
  protected void fillTemplate() throws Exception {

    template.gotoChild();

    template.gotoChild(XFindAlternativeFlightsConstants.TEMPLATE_CURRENCY).addText(request.getCurrency());

    template.gotoChild(XFindAlternativeFlightsConstants.TEMPLATE_MAX_DAYS_OFFSET).addText(String.valueOf(request.getMaxDaysOffset()));

    template.gotoChild(XFindAlternativeFlightsConstants.TEMPLATE_OUTWARD_DAY).addText(DFT.format(request.getOutwardDay()));

    // get the return day node
    XMLTag returnDay = template.gotoChild(XFindAlternativeFlightsConstants.TEMPLATE_RETURN_DAY);
    // if the request has a return date, populate the template
    if (request.getReturnDay() != null) {
      returnDay.addText(DFT.format(request.getReturnDay()));
    }
    // otherwise, delete the return day node from the template
    else {
      template.delete();
    }

    template.gotoChild(XFindAlternativeFlightsConstants.TEMPLATE_ORIGIN).addText(request.getOrigin());

    template.gotoChild(XFindAlternativeFlightsConstants.TEMPLATE_ORIGIN_TYPE).addText(
        request.getOriginType() == CodeType.AIRPORT ? "airport" : "city");

    template.gotoChild(XFindAlternativeFlightsConstants.TEMPLATE_DESTINATION).addText(request.getDestination());

    template.gotoChild(XFindAlternativeFlightsConstants.TEMPLATE_DESTINATION_TYPE).addText(
        request.getDestinationType() == CodeType.AIRPORT ? "airport" : "city");

  }

  @Override
  public XFindAlternativeFlightsResponse handleResponse() throws Exception {
    StringBuilder builder = new StringBuilder(32);
    List<XFindAlternativeFlightsItem> itemList = new ArrayList<XFindAlternativeFlightsItem>();
    String currentName = null;
    XFindAlternativeFlightsItem item = null;
    XFindAlternativeFlightsLegDetails outwardLeg = null;
    XFindAlternativeFlightsLegDetails returnLeg = null;
    CurrentLeg currentLeg = null;

    done: for (int event; ((event = responseReader.next()) != XMLStreamConstants.END_DOCUMENT);) {
      switch (event) {
        case XMLStreamConstants.START_ELEMENT:
          builder.setLength(0);
          currentName = responseReader.getName().getLocalPart();
          if (XFindAlternativeFlightsConstants.RESPONSE_ITEM.equals(currentName)) {
            item = new XFindAlternativeFlightsItem();
          }
          else if (XFindAlternativeFlightsConstants.RESPONSE_OUTWARD.equals(currentName)) {
            outwardLeg = new XFindAlternativeFlightsLegDetails();
            currentLeg = CurrentLeg.OUTWARD;
          }
          else if (XFindAlternativeFlightsConstants.RESPONSE_RETURN.equals(currentName)) {
            returnLeg = new XFindAlternativeFlightsLegDetails();
            currentLeg = CurrentLeg.RETURN;
          }
          break;
        case XMLStreamConstants.CHARACTERS:
          if (item != null) {
            builder.append(responseReader.getTextCharacters(), responseReader.getTextStart(), responseReader.getTextLength());
          }
          break;
        case XMLStreamConstants.END_ELEMENT:
          if (item != null) {
            currentName = responseReader.getName().getLocalPart();
            if (item != null) {
              if (XFindAlternativeFlightsConstants.RESPONSE_ORIGIN.equals(currentName)) {
                item.setOrigin(builder.toString().trim());
              }
              else if (XFindAlternativeFlightsConstants.RESPONSE_ORIGIN_AIRPORT.equals(currentName)) {
                item.setOriginAirport(builder.toString().trim());
              }
              else if (XFindAlternativeFlightsConstants.RESPONSE_DESTINATION.equals(currentName)) {
                item.setDestination(builder.toString().trim());
              }
              else if (XFindAlternativeFlightsConstants.RESPONSE_DESTINATION_AIRPORT.equals(currentName)) {
                item.setDestinationAirport(builder.toString().trim());
              }
              else if (XFindAlternativeFlightsConstants.RESPONSE_PRICE.equals(currentName)) {
                item.setPrice(Double.parseDouble(builder.toString().trim()));
              }
              else if (XFindAlternativeFlightsConstants.RESPONSE_OPERATOR_CODE.equals(currentName)
                  || XFindAlternativeFlightsConstants.RESPONSE_OPERATOR_NAME.equals(currentName)
                  || XFindAlternativeFlightsConstants.RESPONSE_SUPPLIER.equals(currentName)
                  || XFindAlternativeFlightsConstants.RESPONSE_FLIGHT_NO.equals(currentName)
                  || XFindAlternativeFlightsConstants.RESPONSE_AIRCRAFT.equals(currentName)) {
                Method method;
                switch (currentLeg) {
                  case OUTWARD:
                    method = outwardLeg.getClass().getMethod("set" + currentName, String.class);
                    method.invoke(outwardLeg, builder.toString().trim());
                    break;
                  case RETURN:
                    method = returnLeg.getClass().getMethod("set" + currentName, String.class);
                    method.invoke(returnLeg, builder.toString().trim());
                    break;
                }
              }
              else if (XFindAlternativeFlightsConstants.RESPONSE_DEPART_DATE.equals(currentName)
                  || XFindAlternativeFlightsConstants.RESPONSE_ARRIVE_DATE.equals(currentName)) {
                Method method;
                switch (currentLeg) {
                  case OUTWARD:
                    method = outwardLeg.getClass().getMethod("set" + currentName, Date.class);
                    method.invoke(outwardLeg, DFT.parse(builder.toString().trim()));
                    break;
                  case RETURN:
                    method = returnLeg.getClass().getMethod("set" + currentName, Date.class);
                    method.invoke(returnLeg, DFT.parse(builder.toString().trim()));
                    break;
                }
              }
              else if (XFindAlternativeFlightsConstants.RESPONSE_OUTWARD.equals(currentName)) {
                item.setOutwardLeg(outwardLeg);
              }
              else if (XFindAlternativeFlightsConstants.RESPONSE_RETURN.equals(currentName)) {
                item.setReturnLeg(returnLeg);
              }
              else if (XFindAlternativeFlightsConstants.RESPONSE_TIMESTAMP.equals(currentName)) {
                item.setTimestamp(DFT.parse(builder.toString().trim()));
              }
              else if (XFindAlternativeFlightsConstants.RESPONSE_ITEM.equals(currentName)) {
                itemList.add(item);
                item = null;
                outwardLeg = null;
                returnLeg = null;
              }
              break;
            }
          }
          else if (XFindAlternativeFlightsConstants.RESPONSE_FIND_ALTERNATIVE_FLIGHTS.equals(currentName)) {
            break done;
          }
          currentName = null;
          break;
      }
    }
    XFindAlternativeFlightsResponse response = new XFindAlternativeFlightsResponse();
    response.setItemList(itemList);
    return response;
  }
}
